package de.medieninf.android.sensimg;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.os.ConditionVariable;
import android.util.Log;

public class UDPSender implements AccelerationListener {
	final static String TAG = "UDPSender";
	final static String JSON_TEMPLATE = "{\"x\":%f , \"y\":%f, \"z\":%f, \"k\":\"%s\", \"id\": %d}";
	final static String DEFAULT_HOST = "192.168.0.10";
	final static int DEFAULT_PORT = 9617;
	
	private volatile String host;
	private volatile int port;
	private volatile InetAddress inetAddress;
	private DatagramSocket datagramSocket;
	private KeyboardState keyboardState;

	// volatile arrays don't have volatile members
	private volatile float x;
	private volatile float y;
	private volatile float z;
	private volatile boolean sent = true;
	private ConditionVariable doSend = new ConditionVariable();
	private Object lock = new Object();
	
	private volatile SendThread mSendThread = new SendThread();
	private class SendThread extends Thread {
		private int id=1;
		public SendThread() {
			super("SendThread");
			this.start();
		}
		@Override
		public void run() {
			Log.v(TAG, "SendThread started");
			while (mSendThread == Thread.currentThread()) {
				doSend.block();
				float x,y,z;
				boolean sent;
				synchronized (lock) {
					sent = UDPSender.this.sent;
					UDPSender.this.sent = true;
					x = UDPSender.this.x;
					y = UDPSender.this.y;
					z = UDPSender.this.z;
				}
				if (sent) {
					// Log.v(TAG, "SendThread nothing ToDo");
					doSend.close();
					continue; // nothing to be done this round
				}
				// ok, we need to send
				// String packet = String.format(JSON_TEMPLATE, x, y, z, keyboardState.getKeys(), id); // too slow	
				String packet = makeJSONString(x, y, z, keyboardState.getKeys(), id);
				sendString(packet);
				// Log.v(TAG, "message " + packet);
				id += 1;				
			}
			Log.v(TAG, "SendThread terminated");
		}
		
		// instead of String.format, as String.format is horribly slow
		// http://code.google.com/p/android/issues/detail?id=1476
		private String makeJSONString(float x, float y, float z, String keys, int id) {
			StringBuilder sb = new StringBuilder(128);
			sb.append("{");
			sb.append("\"x\": ");
			sb.append(x);
			sb.append(", ");
			sb.append("\"y\": ");
			sb.append(y);
			sb.append(", ");
			sb.append("\"z\": ");
			sb.append(z);
			sb.append(", ");
			sb.append("\"k\": \"");
			sb.append(keys);
			sb.append("\", ");
			sb.append("\"id\": ");
			sb.append(id);
			sb.append("}");
			return sb.toString();
		}
		
		
		private boolean sendString(String s) {
			if (inetAddress == null) {
				try {
					inetAddress = InetAddress.getByName(host);
					// Log.v(TAG, "got inetAddress " + inetAddress ); 
				} catch (UnknownHostException e) {
					Log.w(TAG, "Error inetAddress " + e.getMessage()); 
					return false;
				}
			}
			if (datagramSocket == null) {
				try {
					datagramSocket = new DatagramSocket();
					// Log.v(TAG, "got datagramSocket ");
				} catch (SocketException e) {
					Log.w(TAG, "Error datagramSocket " + e.getMessage());
					return false;
				}
			}
		    try {
		        byte[] message = s.getBytes();
		        DatagramPacket packet = new DatagramPacket(
		        		message, message.length,
		        		inetAddress, port);
		        datagramSocket.send(packet);
		        // Log.v(TAG, "packet sent to " + inetAddress + ":" + port);
		    } catch (IOException e) {
		        Log.w(TAG, "Error sending packet " + e.getMessage());
		    	return false;
		    }
		    return true;
		}
	};
	
	public UDPSender(KeyboardState keyboardState) {
		host = DEFAULT_HOST;
		port = DEFAULT_PORT;
		this.keyboardState = keyboardState;
	}
	
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
		inetAddress = null;
	}
	
	public int getPort() {
		return this.port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	
	@Override
	public void update(float x, float y, float z) {
		boolean oldSent;
		// drop of load
		synchronized (lock) {
			oldSent = sent;
			sent = false; // need to send
			this.x = x;
			this.y = y;
			this.z = z;
			doSend.open();
		}
		if (oldSent == false)  {
			Log.v(TAG, "Sensor values have been dropped");
		}
	}
	
	public void close() {
		mSendThread = null; // ends sending thread
		if (datagramSocket != null) {
			datagramSocket.close();
		}
	}
}
